/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.icee.myth.log;

import com.icee.myth.log.message.FileDBDownCacheGameLogMessage;
import com.icee.myth.log.message.FileDBErrorGameLogMessage;
import com.icee.myth.log.message.FileDebugGameLogMessage;
import com.icee.myth.log.message.FileNetErrorGameLogMessage;
import com.icee.myth.log.message.GameLogMessage;
import com.icee.myth.utils.Consts;
import com.icee.myth.utils.LinkedTransferQueue;
import java.io.BufferedWriter;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.text.FieldPosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author liuxianke
 */
public class FileLogThreadHandle implements Runnable {

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private final LinkedTransferQueue<GameLogMessage> q;
    private BufferedWriter debugBw;
    private BufferedWriter dberrBw;
    private BufferedWriter neterrBw;
    private DataOutputStream dbDownCacheDOS;   // 数据库断线后缓存未写入数据库的数据

    private final String rootPath;
    private final String pathName;
    private final int flushInterval;
    private final int bufferSize;
    private final boolean needConsolePrint;

    private long realPrevTime;
    private long realCurrTime;

    public boolean shutdown = false;

    public FileLogThreadHandle(String rootPath, String pathName, int flush_interval, int bufferSize, boolean needConsolePrint) {
        q = new LinkedTransferQueue<GameLogMessage>();

        this.rootPath = rootPath;
        this.pathName = pathName;
        this.flushInterval = flush_interval;
        this.bufferSize = bufferSize;
        this.needConsolePrint = needConsolePrint;
    }

    private String now() {
        return sdf.format(new Date());
    }

    private void createDirectoryIfNotExist(String path) {
        File dir = new File(path);
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }

    public void log(GameLogMessage message) {
        q.offer(message);
    }

    private void setFileRef() {
        try {
            createDirectoryIfNotExist(rootPath + pathName + "/debug");
            createDirectoryIfNotExist(rootPath + pathName + "/error");

            StringBuffer dateStringBuffer = new SimpleDateFormat("yyyy.MM.dd").format(new Date(realCurrTime), new StringBuffer(), new FieldPosition(0));
            String dateString = dateStringBuffer.toString();

            if (debugBw != null) {
                debugBw.close();
            }
            debugBw = new BufferedWriter(new FileWriter(rootPath + pathName + "/debug/debug" + dateString + ".log", true), bufferSize);

            if (dberrBw != null) {
                dberrBw.close();
            }
            dberrBw = new BufferedWriter(new FileWriter(rootPath + pathName + "/error/dberror" + dateString + ".log", true), bufferSize);

            if (neterrBw != null) {
                neterrBw.close();
            }
            neterrBw = new BufferedWriter(new FileWriter(rootPath + pathName + "/error/neterror" + dateString + ".log", true), bufferSize);
        } catch (IOException ex) {
            Logger.getLogger(FileLogThreadHandle.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void run() {
        realCurrTime = realPrevTime = System.currentTimeMillis();

        // 初始日志文件引用(根据realCurrTime的值)
        setFileRef();
        
        try {
            while (!shutdown) {
                GameLogMessage message = q.poll();
                while (message != null) {
                    switch (message.type) {
                        case GAMELOGTYPE_F_DEBUG: {
                            handleFileDebugGameLogMessage((FileDebugGameLogMessage) message);
                            break;
                        }
                        case GAMELOGTYPE_F_DBERR: {
                            handleFileDBErrorGameLogMessage((FileDBErrorGameLogMessage) message);
                            break;
                        }
                        case GAMELOGTYPE_F_NETERR: {
                            handleFileNetErrorGameLogMessage((FileNetErrorGameLogMessage) message);
                            break;
                        }
                        case GAMELOGTYPE_F_DBDOWN_CACHE: {
                            handleFileDBDownCacheGameLogMessage((FileDBDownCacheGameLogMessage) message);
                        }
                        default: {
                            // TODO: 写错误日志
                            break;
                        }
                    }
                    message = q.poll();
                }

                Thread.sleep(100);

                // auto-flush every flush_interval milliseconds
                realCurrTime = System.currentTimeMillis();
                if (realCurrTime - realPrevTime>= flushInterval) {
                    debugBw.flush();
                    dberrBw.flush();
                    neterrBw.flush();

                    // 跨天重建日志文件
                    if ((realCurrTime + Consts.JET_LAG)/Consts.MILSECOND_ONE_DAY > (realPrevTime + Consts.JET_LAG)/Consts.MILSECOND_ONE_DAY) {
                        setFileRef();
                    }

                    realPrevTime = realCurrTime;
                }
            }

            debugBw.flush();
            debugBw.close();
            
            dberrBw.flush();
            dberrBw.close();
            
            neterrBw.flush();
            neterrBw.close();

            if (dbDownCacheDOS != null) {
                dbDownCacheDOS.flush();
                dbDownCacheDOS.close();
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        }
    }

    private void handleFileDebugGameLogMessage(FileDebugGameLogMessage message) {
        try {
            StringBuilder stringBuilder = new StringBuilder(now());
            switch (message.subType) {
                case DEBUGLOGTYPE_ERROR: {
                    stringBuilder.append("\t[ERROR]: ").append(message.msg);
                    break;
                }
                case DEBUGLOGTYPE_WARN: {
                    stringBuilder.append("\t[WARNING]: ").append(message.msg);
                    break;
                }
                case DEBUGLOGTYPE_INFO: {
                    stringBuilder.append("\t[INFO]: ").append(message.msg);
                    break;
                }
                case DEBUGLOGTYPE_DEBUG: {
                    stringBuilder.append("\t[DEBUG]: ").append(message.msg);
                    break;
                }
            }

            String str = stringBuilder.toString();
            if (needConsolePrint) {
                System.err.println(str);
            }

            debugBw.write(str);
            debugBw.newLine();
        } catch (IOException ex) {
            Logger.getLogger(GameLogger.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void handleFileDBErrorGameLogMessage(FileDBErrorGameLogMessage message) {
        try {
            StringBuilder stringBuilder = new StringBuilder(now());
            stringBuilder.append("\t").append(message.msg);

            String str = stringBuilder.toString();
            if (needConsolePrint) {
                System.err.println(str);
            }

            dberrBw.write(str);
        } catch (IOException ex) {
            Logger.getLogger(GameLogger.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void handleFileNetErrorGameLogMessage(FileNetErrorGameLogMessage message) {
        try {
            StringBuilder stringBuilder = new StringBuilder(now());
            stringBuilder.append("\t").append(message.msg);

            String str = stringBuilder.toString();
            if (needConsolePrint) {
                System.err.println(str);
            }

            neterrBw.write(str);
        } catch (IOException ex) {
            Logger.getLogger(GameLogger.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void handleFileDBDownCacheGameLogMessage(FileDBDownCacheGameLogMessage message) {
        try {
            if (dbDownCacheDOS == null) {
                // 初始数据库断线缓存文件
                dbDownCacheDOS = new DataOutputStream(new FileOutputStream(rootPath + pathName + "/error/dbcache.dbdowncache", false));
            }

            int messageLen = message.data.length;

            dbDownCacheDOS.writeInt(messageLen);
            dbDownCacheDOS.write(message.data);
        } catch (IOException ex) {
            Logger.getLogger(GameLogger.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}